// 手动实现 JSON.parse
{
  var jsonStr = '{ "age": 20, "name": "jack" }';

  // 第一种方式使用eval，但是会xss漏洞攻击，所以要进行正则过滤
  function jsonParse(opt) {
    return eval('(' + opt + ')'); // 中间要去过滤一下
  }
  console.log(jsonParse(jsonStr));

  // 方式二:Function与eval有相同的字符串参数特性功能
  // var func = new Function(arg1, arg2, ..., functionBody);
  var json = new Function('return ' + jsonStr)();
}

// 手写实现一个 JSON.stringify语法功能
{
  /* 
  Boolean | Number| String 类型会⾃自动转换成对应的原始值。
  undefined 、任意函数以及 symbol ，会被忽略略（出现在⾮非数组对象的属性值中时），或者被转换
  成  null （出现在数组中时）。
  不不可枚举的属性会被忽略略
  如果⼀一个对象的属性值通过某种间接的⽅方式指回该对象本身，即循环引⽤用，属性也会被忽略略
  */
  function jsonStringify(obj) {
    let type = typeof obj;
    if (type !== 'object' || type === null || obj === null) {
      if (/string|undefined|function/.test(type)) {
        obj = `"${obj}"`;
      }
      if (obj === null) obj = `"null"`;
      return String(obj);
    }
    let json = [];
    const isArr = obj && obj.constructor === Array;

    const startStr = isArr ? '[' : '{';
    const endStr = isArr ? ']' : '}';
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        let tempKey = isArr ? '' : `"${key}": `; // 定义key前缀，数组为空字符，其它情况为 "key":
        json.push(`${tempKey}${jsonStringify(obj[key])}`); // key追加value值
      }
    }

    return `${startStr}${json}${endStr}`;
  }

  // 测试演示
  const testJson = {
    x: 5,
    c: { d: false, e: function (params) {} },
    base: undefined,
    umi: ['hi', null, { age: 33, book: 'js' }],
    flutter: {},
    data: [1, 'false', false],
  };
  console.log(jsonStringify(testJson));
  // { "x": "5","c": { "d": "false","e": "function (params) {}" },"base": "undefined","umi": [ "hi","null",{ "age": "33","book": "js" } ],"flutter": {  },"data": [ "1","false","false" ] }
}

// JavaScript中如何检测一个变量是一个String类型？请写出函数实现
{
  /* 
  typeof obj        typeof(obj)实现方法
  obj.constructor === String
  toString.call(obj) === [object String]
  */
}

// 实现new ⼀个Fn操作符
{
  function New(func) {}
  function A() {}
  New(A);
  var obj = new A(1, 2);

  /* 来源：「你不不知道的javascript」 英⽂文版
  new 操作符做了了这些事：
  它创建了了⼀一个全新的对象。
  它会被执行 [[Prototype]] （也就是 __proto__ ）链接。
  它使 this 指向新创建的对象。。
  通过 new 创建的每个对象将最终被 [[Prototype]] 链接到这个函数的 prototype 对象上。
  如果函数没有返回对象类型 Object (包含 Functoin, Array, Date, RegExg, Error )，那么 new
  表达式中的函数调⽤用将返回该对象引用。
  */
  function New(func) {
    var res = {};
    if (func.prototype !== null) {
      // 创建了⼀一个全新的对象，而res原型链指向func对象
      res.__proto__ = func.prototype;
    }

    // 改变func的this指向到res对象，传入N参数
    // Array.prototype.slice.call()能把类数组对象转化成数组
    var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
    // 如果函数有返回对象类型 Object (包含 Functoin, Array, Date, RegExg, Error )，者返回修正后的ret
    if (
      (typeof ret === 'object' || typeof ret === 'function') &&
      ret !== null
    ) {
      return ret;
    }
    return res;
  }
  function A() {}
  var obj1 = New(A); // new实例化

  var obj2 = new A(1, 2);
  var obj3 = new A(1, 2);
  console.log('new后的', obj1);
  /* 
  new 的原理实现
  先拿到把传入的函数的prototype 然后获取传入的第二个和之后的参数, 利用apply把this绑定到prototype上传入并且传入其他参数 最后返回绑定好的对象
  */
}

/* 实现一个 call 或 apply
call 语法：
  fun.call(thisArg, arg1, arg2, ...) ，调⽤用⼀个函数, 其具有⼀个指定的this值和分别地
  提供的参数(参数的列列表)。

apply 语法：
  func.apply(thisArg, [argsArray]) ，调⽤用一个函数，以及作为⼀个数组（或类似数组对
象）提供的参数。
*/
{
  // call实现代码
  Function.prototype.call2 = function (content = window) {
    content.fn = this; // 创建自身一个fn函数
    let args = [...arguments].slice(1);
    let result = content.fn(...args);
    delete content.fn;
    return result;
  };
  var foo = {
    value: 1,
  };
  function bar(name, age) {
    console.log(name);
    console.log(age);
    console.log(this.value);
  }
  bar.call2(foo, 'black', '18'); // black 18 1
  /* 
  解析实现，首先通过在window全局挂载一个新的fn函数，并且新的fn函数中的this绑定到了，调用当前函数载体（bar调用的，所以此时this是指向bar）
  接着做的事情就是过滤拿到真实的第二个参数及以后的参数，然后传入fn当中。
  传入后返回新的fn函数，此函数this是已经指定了调用者。
  */

  // 实现apply， apply() 的实现和 call() 类似，只是参数形式不不同
  Function.prototype.apply2 = function (context = window) {
    context.fn = this;
    let result;
    // 判断是否有第⼆个参数
    if (arguments[1]) {
      result = context.fn(...arguments[1]);
    } else {
      result = context.fn();
    }
    delete context.fn;
    return result;
  };
  bar.apply2(foo, ['black', '18']); // black 18 1
}

// 实现⼀个 Function.bind()
{
  /* 
  bind() ⽅方法: 会创建⼀一个新函数。当这个新函数被调⽤用时，bind() 的第⼀一个参数将作为它运⾏行行时的 this，之后的⼀一序列列参数将会在传递的实参前传⼊入作为它的参数。(来⾃自于 MDN )
  */
  Function.prototype.bind2 = function (content) {
    if (typeof this != 'function') {
      throw Error('not a function');
    }
    // 若没问参数类型则从这开始写
    let fn = this;
    let args = [...arguments].slice(1);
    let resFn = function () {
      return fn.apply(
        this instanceof resFn ? this : content,
        args.concat(...arguments)
      );
    };
    function tmp() {}
    tmp.prototype = this.prototype;
    resFn.prototype = new tmp();
    return resFn;
  };

  var foo = {
    value: 1,
  };
  function bar(name, age) {
    console.log(name);
    console.log(age);
    console.log(this.value);
  }
  let _testBind = bar.bind2(foo, 'black', '18'); // black 18 1
  console.log(_testBind.prototype); // this指向bar
  _testBind();
}

// 实现⼀个继承，寄⽣生组合式继承
{
  // ⼀般只建议写这种，因为其它⽅方式的继承会在⼀次实例例中调⽤用两次父类的构造函数或有其它缺点。
  // 核⼼心实现是：用⼀个 F 空的构造函数去取代执行了 Parent 这个构造函数。
  function Parent(name) {
    this.name = name;
  }
  Parent.prototype.sayName = function () {
    console.log('parent name:', this.name);
  };
  function Child(name, parentName) {
    Parent.call(this, parentName);
    this.name = name;
  }
  function create(proto) {
    function F() {}
    F.prototype = proto;
    return new F();
  }
  Child.prototype = create(Parent.prototype);
  Child.prototype.sayName = function () {
    console.log('child name:', this.name);
  };
  Child.prototype.constructor = Child;
  var parent = new Parent('father');
  parent.sayName(); // parent name: father
  var child = new Child('son', 'father');
}

// 实现⼀个JS函数柯里化
// 函数柯里化的主要作用和特点就是参数复用、提前返回和延迟执行。
{
  // 实现能够像如下案例输出的值
  curryTest(1, 2, 3)(4); //返回10
  curryTest(1, 2)(4)(3); //返回10
  curryTest(1, 2)(3, 4); //返回10

  // 实现版本
  const curry = (fn, ...args) => {
    // fn就是sum函数，当sum所有参数(4个)等于args时
    if (fn.length === args.length) {
      return fn(...args); // 把所有参数整合传入给sum计算
    }
    // 整合拼接()()后续参数，通过递归
    return (...rest) => curry(fn, ...args, ...rest);
  };
  const sum = (a, b, c, d) => a + b + c + d;
  let curryTest = curry(sum);
  curryTest(1, 2, 3)(4); //返回10
  curryTest(1, 2)(4)(3); //返回10
  curryTest(1, 2)(3, 4); //返回10
}

// 手写⼀个 Promise (中⾼高级必考)
{
  // onFulfilled ⽤用来接收promise成功的值
  // onRejected ⽤用来接收promise失败的原因
  promise1 = promise.then(onFulfilled, onRejected);

  // 面试够用版本
  function myPromise(constructor) {
    let self = this;
    self.status = 'pending'; //定义状态改变前的初始状态
    self.value = undefined; //定义状态为resolved的时候的状态
    self.reason = undefined; //定义状态为rejected的时候的状态
    function resolve(value) {
      //两个==="pending"，保证了了状态的改变是不不可逆的
      if (self.status === 'pending') {
        self.value = value;
        self.status = 'resolved';
      }
    }
    function reject(reason) {
      //两个==="pending"，保证了了状态的改变是不不可逆的
      if (self.status === 'pending') {
        self.reason = reason;
        self.status = 'rejected';
      }
    }
    //捕获构造异常
    try {
      constructor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

  myPromise.prototype.then = function (onFullfilled, onRejected) {
    let self = this;
    switch (self.status) {
      case 'resolved':
        onFullfilled(self.value);
        break;
      case 'rejected':
        onRejected(self.reason);
        break;
      default:
    }
  };

  var p = new myPromise(function (resolve, reject) {
    resolve(1);
  });
  p.then(function (x) {
    console.log(x); //输出
  });

  // 实现promise方法,功能全
  // 文章解析 https://zhuanlan.zhihu.com/p/58428287
  class MyPromise {
    PENDING = 'pending'; // 运行中状态
    FULFILLED = 'fulfilled'; // 成功状态
    REJECTED = 'rejected'; // 失败状态
    state = this.PENDING; // 当前Promise状态
    callbacks = []; // 回调，待执行的.then回调函数
    value = null; // 返回的结果

    constructor(fn) {
      fn(this._resolve, this._reject);
    }

    // 错误捕获方法
    catch = (onError) => {
      return this.then(null, onError);
    };

    finally(onDone) {
      if (typeof onDone !== 'function') return this.then();
      let _Promise = this.constructor;
      return this.then(
        (value) => _Promise.resolve(onDone()).then(() => value),
        (reason) => {
          return _Promise.resolve(onDone()).then(() => {
            throw reason;
          });
        }
      );
    }

    // 链式调用promise
    then = (onFulfilled, onRejected) => {
      // 每次返回新的对象，解决链式调用，并且成功的值只给下次.then使用
      return new MyPromise((resolve, reject) => {
        this._handle({
          onFulfilled: onFulfilled || null,
          onRejected: onRejected || null,
          resolve: resolve,
          reject: reject,
        });
      });
    };

    _handle(callback) {
      if (this.state === this.PENDING) {
        this.callbacks.push(callback);
        return;
      }

      const isFulfilled = this.state === this.FULFILLED; // 判断当前状态是否成功
      // 根据promise状态值,动态决定调用.then还是catch函数
      const cb = isFulfilled ? callback.onFulfilled : callback.onRejected;
      // 根据promise状态值,动态获取具体的成功或失败函数
      let statusCb = isFulfilled ? callback.resolve : callback.reject;

      if (!cb) {
        // 如果then中没有传递任何东西
        statusCb(this.value);
        return;
      }

      let ret; // 返回值
      try {
        ret = cb(this.value); // 获取.then/.catch函数返回的值
      } catch (error) {
        ret = error; // 错误捕获
        statusCb = callback.reject; // 更改执行函数,调用错误回调
      }

      // 动态执行resolve或reject函数
      statusCb(ret); // 返回给Promise结果中去,供.then可以接收参数
    }

    // 成功回调函数
    _resolve = (value) => {
      // 追加功能，在真实接口异步请求中,待看
      // if (value && (typeof value === 'object' || typeof value === 'function')) {
      //   var then = value.then;
      //   if (typeof then === 'function') {
      //     then.call(value, this._resolve.bind(this));
      //     return;
      //   }
      // }

      this.state = this.FULFILLED; // 改变状态-成功
      this.value = value;
      // 当reolve同步执行时，虽然callbacks为空，回调函数还没有注册上来，但没有关系，因为后面注册上来时
      this.callbacks.forEach((callbackFn) => callbackFn(value)); // 执行所有队列中的所有.then函数
    };

    // 失败回调函数
    _reject = (error) => {
      this.state = this.REJECTED; // 改变状态-失败
      this.value = error;
      this.callbacks.forEach((callbackFn) => callbackFn(error));
    };
  }

  let p = new MyPromise((resolve, reject) => {
    console.log('done');
    resolve('5秒');
    // reject('出错了');
  })
    .then((tip) => {
      console.log('then1', tip);
      return 222;
    })
    .then((tip) => {
      console.log('then2', tip);
    })
    .then((d) => {
      console.log('dd>>>', d);
    })
    .catch((e) => {
      console.log('err>>>', e);
    })
    .then((d) => {
      console.log('dd>>>', d);
    })
    .finally(() => {
      console.log('onDone');
    })
    .then((d) => {
      console.log(d);
    });
}

/* 
手写实现promise.all的方法
总结 promise.all 的特点
1、接收一个 Promise 实例的数组或具有 Iterator 接口的对象，
2、如果元素不是 Promise 对象，则使用 Promise.resolve 转成 Promise 对象
3、如果全部成功，状态变为 resolved，返回值将组成一个数组传给回调
4、只要有一个失败，状态就变为 rejected，返回值将直接传递给回调all() 的返回值也是新的 Promise 对象
*/
{
  // 实现promiseAll函数
  function promiseAll(promises) {}
  function testFn() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(3);
      }, 3000);
    });
  }

  promiseAll([1, 2, testFn(), 4, 5]).then((data) => {
    console.log(data);
  });

  // 以下为实现版本
  function promiseAll(promises) {
    return new Promise(function (resolve, reject) {
      if (!Array.isArray(promises)) {
        return reject(new TypeError('arguments must be an array'));
      }
      const resolvedCounter = 0;
      const promiseNum = promises.length;
      let resolvedValues = []; // 返回的数据

      for (let i = 0; i < promiseNum; i++) {
        Promise.resolve(promises[i]).then(
          function (value) {
            resolvedCounter++;
            resolvedValues[i] = value;
            if (resolvedCounter == promiseNum) {
              return resolve(resolvedValues);
            }
          },
          function (reason) {
            return reject(reason);
          }
        );
      }
    });
  }
}

// ⼿手写防抖( Debouncing )和节流( Throttling )
{
  // 防抖版本
  function debounce(fn, time) {
    let _time;
    return (...arg) => {
      if (_time) clearTimeout(_time);
      _time = setTimeout(() => {
        fn(...arg);
      }, time);
    };
  }
  let deFn = debounce(() => {
    console.log('测试');
  }, 1000);

  // 节流版本
  function throttle(fn, time) {
    let flag = false;
    return (...arg) => {
      if (flag) return;
      flag = true;
      setTimeout(() => {
        fn.call(this, ...arg);
        flag = false;
      }, time);
    };
  }

  let thrFn = throttle((i) => {
    console.log('测试', i);
  }, 100);
}

// 实现deepcopy(深拷贝)
{
  var obj1 = {
    // 这是数据源，几种实现方式都用这个源数据演示
    a: 'hello',
    b: {
      a: 'hello-a',
      b: 21,
    },
    c: {
      fn: function () {
        console.log('222');
      },
      body: {
        text: 'ye',
      },
      list: [
        1,
        5,
        '32jjj4',
        2,
        function () {
          console.log(333);
        },
      ],
    },
    say: function () {
      console.log('hi');
    },
    un: undefined,
  };

  // 方式1：通过json对象转换实现拷贝，在对象中有函数的时候是不行的
  function deepClone2(obj) {
    var _obj = JSON.stringify(obj); // 转换成字符串
    objClone = JSON.parse(_obj); // 在转换成对象
    return objClone; // 返回转换后的对象
  }
  var cloneObj1 = deepClone2(obj1);
  cloneObj1.a = 'zzq';
  cloneObj1.b.a = 'chan';
  // 查看数据源头是否被修改
  console.log(obj1.a);
  console.log(obj1.b.a);

  // 方式2：把每个对象值赋值给新的变量（通过循环递归全复制过去）
  function deepCopy(obj) {
    let result = Array.isArray(obj) ? [] : {};
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (typeof obj[key] === 'object') {
          result[key] = deepCopy(obj[key]); // 递归复制
        } else {
          result[key] = obj[key];
        }
      }
    }
    return result;
  }
  var cloneObj1 = deepCopy(obj1);
  cloneObj1.a = 'zzq';
  cloneObj1.b.a = 'chan';
  // 查看数据源头是否被修改
  console.log(obj1.a);
  console.log(obj1.b.a);
}

// 手写实现reduce方法，
{
  Array.prototype.reduce2 = function (fn, defaultData) {}; // 实现区域
  var arr2 = [4, 21, 3, 5]; // 累加33
  function getFn2(total, currentValue, currentIndex, arr) {
    return total + currentValue;
  }
  console.log(arr2.reduce2(getFn2, 0)); // 最终输出 33

  // 实现代码如下
  Array.prototype.reduce2 = function (fn, defaultData) {
    const arr = Array.isArray(this) ? this : [];
    let lastItem = defaultData;
    if (isNaN(lastItem) || typeof lastItem === 'undefined')
      lastItem = arr.shift();

    for (let i = 0; i < arr.length; i++) {
      lastItem = fn(lastItem, arr[i], i, arr);
    }
    return lastItem;
  };
}

// 手写 Koa 的 compose 方法
{
  // 这里实现一个简化版，如果问你异步如何处理的话，dispatch 函数里面 return promise 就好了
  function compose(middleware) {
    return function () {
      return dispatch(0);
      function dispatch(i) {
        let fn = middleware[i];
        if (!fn) return;
        return fn(function next() {
          return dispatch(i + 1);
        });
      }
    };
  }
}

// 如何模拟实现 Array.prototype.splice
{
  Array.prototype.splice2 = function () {};

  // 实现如下
  Array.prototype.splice2 = function (start, deleteCount, ...addList) {
    if (start < 0) {
      if (Math.abs(start) > this.length) {
        start = 0;
      } else {
        start += this.length;
      }
    }

    if (typeof deleteCount === 'undefined') {
      deleteCount = this.length - start;
    }

    const removeList = this.slice(start, start + deleteCount);

    const right = this.slice(start + deleteCount);

    let addIndex = start;
    addList.concat(right).forEach((item) => {
      this[addIndex] = item;
      addIndex++;
    });
    this.length = addIndex;

    return removeList;
  };

  console.log([1, 2, 3, 4, 5, 6].splice(1, 4));
}
